package com.java.base.exercise.limitrate;

import lombok.SneakyThrows;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author RenPu
 * @Date 2023/3/17 14:06
 * @Version 1.0
 * @Description:基于计数器完成限流的实现
 *             限流算法：滑动窗口
 *             优点：将窗口在细分，并且按照时间滑动，避免了计数器实现限流中，在时间临界点产成double的请求量
 *             缺点：时间区间的精确度越高，算法所需要的空间容量就越大
 **/
public class SlidingWindowLimiter {

    /**
     * 循环队列，就是封装多个窗口用，该数量是windowSize的2倍，
     * 防止窗口在滑动的时候，出现滑动超出范围的现象
     */
    private AtomicInteger [] timeSlices;

   /**
    * 队列的总长度
    */
   private int timeSizes;

    /**
     * 每个时间片的时长，单位以毫秒为单位
     */
   private int timeMillisPerSlice;

    /**
     *共有多少个时间片，即指窗口长度
     */
   private int windowSize;

    /**
     *在一个完整窗口，允许通过的最大请求数阀值
     */
   private int threshold;

    /**
     * 该滑窗的起始创建时间，也就是第一个数据
     */
   private long beginTimeStamp;


    /**
     *最后一个数据的时间戳
     */
   private long lastAddTimeStamp;


    public SlidingWindowLimiter() {
    }

    public SlidingWindowLimiter(int timeMillisPerSlice, int windowSize, int threshold) {
        this.timeMillisPerSlice = timeMillisPerSlice;
        this.windowSize = windowSize;
        this.threshold = threshold;
        //保证存储至少为2个窗口
        this.timeSizes=windowSize*2;
        
        reset();
    }


    /**
     * 初始化队列，为了节省空间采用懒加载的形式初始化
     */
    private void reset() {
        beginTimeStamp=System.currentTimeMillis();

        //校验窗口容器是否进行初始化
        if(timeSlices!=null){
            return;

        }
        //初始化窗口容器
        AtomicInteger[] localTimeSelics = new AtomicInteger[timeSizes];
        for (int i = 0; i <timeSizes ; i++) {
            localTimeSelics[i]=new AtomicInteger(0);

        }
        timeSlices=localTimeSelics;
    }


    @SneakyThrows
    public static void main(String[] args) {

        //初始化时间周期为100毫秒，窗口为4，请求限制数量为8
        SlidingWindowLimiter window = new SlidingWindowLimiter(100,4,8);

        for (int i = 0; i <100 ; i++) {
            System.out.println(window.addCount(2));

            window.print();
            System.out.println("========================");

            Thread.sleep(102);


        }




    }


    /**
     * 遍历展示窗口容器内的元素
     */
    private void print() {

        for(AtomicInteger atomicInteger: timeSlices){
            System.out.println(atomicInteger+"-");

        }
    }


    /**
     * 计算当前时间片位于那个窗口
     * @return
     */
    private int locationIndex(){

        long now = System.currentTimeMillis();

        //如果当前key已经超出一整个时间片，不需要进行计算了，直接进行初始化
        if(now-lastAddTimeStamp>timeMillisPerSlice*windowSize){
           reset(); //初始化操作
        }

        return (int) (((now-beginTimeStamp)/timeMillisPerSlice)%timeSizes);


    }



    private boolean addCount(int i) {

        int index = locationIndex();
        System.out.println("index:"+index);
        return true;
    }
}
